/*  
  -- The Lime II Project -- 

  A tuplespaces-based middleware for coordinating agents and mobile hosts.
  Copyright (C) 2005.
  Gian Pietro Picco, Amy L. Murphy, Lorenzo Bellini.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
*/

package lime2.trackerca;
import lime2.ca.*;
import lime2.*;

import java.io.*;
import java.net.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.collections.*;





/**
 * A TCP/UDP based communication adapter. It uses TCP for unicast communication and UDP for multicast. 
 * 
 * @author Gian Pietro Picco
 * @author Amy Murphy
 * @author Lorenzo Bellini 
 */

public class TrackerPlainCA implements ICommunicationAdapter
{
	/**
	 *  Tracker
   */
	public static Hashtable<Integer,LimeServerID> serverlist=new Hashtable<Integer,LimeServerID> ();
	/**
	 * engagedlist <host,neighbors>
	 */

	public static Hashtable<LimeServerID,List<LimeServerID>> engagedlist=new Hashtable <LimeServerID,List<LimeServerID>>();
	
	private void getTrackerServers() 
	{	
	try
		{	
		String path=this.getClass().getResource("config/TrackerServer.xml").toExternalForm();
		XMLConfiguration config=new XMLConfiguration(path);
		List list=config.configurationsAt("server");
		Iterator it=list.iterator();
		while(it.hasNext())
		{
			 HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next();
			 int ID=sub.getInt("[@ID]");
			 InetAddress IP = InetAddress.getByName(sub.getString("IP"));
			 int port = sub.getInt("port");
			 serverlist.put(ID,new LimeServerID(IP,port));
		}	
		}
		catch (ConfigurationException e){e.getStackTrace();}
		catch(IOException e){
			e.getStackTrace();
			}
	}
	
	private void getEngaged()
	{
		try
		{
		String path=this.getClass().getResource("config/TrackerServer.xml").toExternalForm();
		XMLConfiguration config=new XMLConfiguration(path);
		List list=config.configurationsAt("engageds.engaged");
		Iterator it=list.iterator();
		while(it.hasNext())
		{
			 HierarchicalConfiguration sub = (HierarchicalConfiguration) it.next();
			 int serverID=sub.getInt("[@serverID]");
			 List neighborIDlist=sub.getList("neighborID");
			 List <LimeServerID>neighborlist=new ArrayList <LimeServerID>();
			 for(int i=0;i<neighborIDlist.size();i++)
			 {
				 neighborlist.add(serverlist.get(Integer.parseInt((neighborIDlist.get(i)).toString())));
			 }
			 engagedlist.put(serverlist.get(serverID), neighborlist);
			}	
		}
		catch (ConfigurationException e){
			System.out.println(e.getMessage());
			e.getStackTrace();
			}
		catch(Exception e)
		{
			System.out.println(e.getMessage());
		}
	}
	


 // datagrams sender socket
 static DatagramSocket ssock;	
	
 // multicast socket
 static MulticastSocket msock;
 
 // multicast group address
 static InetAddress maddr;
	 
 // multicast group port
 static int mport;	 
 
 // internal queue for incoming remote ops
 static Queue queue; 

 // unicast receiver
 static TrackerPlainUnicastReceiver pur; 
 
 // multicast receiver
 static TrackerPlainMulticastReceiver tpmr;
 
 // detector
 static TrackerPlainDetector tpd; 
 

 
 /**
  * This method must be called before the communication adapter is used; it performs all the necessary initializations
  * (i.e. starting thread/s, establishing permanent connections, etc..) 
  * 
  * @throws LimeCommunicationException
  */
 public void init() throws LimeCommunicationException
 {
	 
	 /**
	  *  Tracker
	  *  
	  */ 
	getTrackerServers();
	getEngaged();
  // temporary variable
  int uport = LimeServer.getServer().getUnicastPort();	
 	
  // initializations	
  mport = LimeServer.getServer().getMulticastPort();	
  maddr = LimeServer.getServer().getMulticastID().getHost();
  queue = new Queue();
  
  // creates receiving unicast socket
  ServerSocket usock = null;
  try { usock = new ServerSocket(uport); } 
  catch (IOException ioe) { throw new LimeCommunicationException("can't listen on unicast port (TCP " + uport + ")", ioe, Failures.CRITICAL); }
    
  // creates datagrams sender socket
  ssock = null;
  try { ssock = new DatagramSocket(uport); }
  catch (IOException ioe) { throw new LimeCommunicationException("can't use unicast port (UDP " + uport + ")", ioe, Failures.CRITICAL); }
  
  // creates receiving multicast socket
  msock = null;
  try { msock = new MulticastSocket(mport); }
  catch (IOException ioe) { throw new LimeCommunicationException("can't listen to multicast group " + maddr, ioe, Failures.CRITICAL); }
          
  // threading (.. please note that instances of Queue are internally synchronized to prevent concurrent access)

  pur = new TrackerPlainUnicastReceiver(usock);
  tpmr = new TrackerPlainMulticastReceiver(msock, ssock, uport);
  pur.start();
  tpmr.start();
  
  // creates detector
  tpd = new TrackerPlainDetector();
  
  /**
   *  Tracker 
   */
 }
		
 /**
  * Enables this host to join the system. The serializable object may serve to provide startup information for the 
  * joining process (i.e. a host to attach to in a dispatching tree). Please note that communication exceptions are 
  * not thrown; when this method is invoked, it is guaranteed that the LimeServer will join the system as soon as 
  * possible.
  * 
  * @param info startup information for the joining process
  */	
 public void joinSystem(Serializable info) 
 {
  
 }

 /** 
  * Enables this host to leave the system. Please note that communication exceptions are not thrown; when this method
  * is invoked, it is guaranteed that the LimeServer will eventually leave the system. 
  */
 public void leaveSystem() 
 {

 }

 /**
  * Declares that from now on this node is interested in everything that concerns a specific tuplespace. Please note 
  * that communication exceptions are not thrown; when this method is invoked, it is guaranteed that the LimeServer 
  * will join the group as soon as possible.
  * 
  * @param name name of the tuplespace we are interested in
  */ 
 public void joinGroup(String name) 
 {
  // does nothing	
 }
	 
 /**
  * Declares that from now on this node is uninterested in everything that concerns a specific tuplespace. Please note
  * that communication exceptions are not thrown; when this method is invoked, it is guaranteed that the LimeServer 
  * will eventually leave the group.
  * 
  * @param name name of the tuplespace we are no more interested in
  */ 
 public void leaveGroup(String name) 
 {
  // does nothing	
 }

 /**
  * Sends an operation request to all the group members.
  * 
  * @param name name of the destination tuplespace 
  * @param d operation descriptor
  * @throws LimeCommunicationException
  */
 public void sendOp(String name, RemoteOp d) throws LimeCommunicationException
 {
  sendGroup(d); 
 }

 /**
  * Sends an operation request to a specific host.
  * 
  * @param host destination host
  * @param d operation descriptor
  * @throws LimeCommunicationException
  */
 public void sendOp(LimeServerID host, RemoteOp d) throws LimeCommunicationException
 {
  send(host, d); 
 }

 /**
  * Sends a reaction registration request to all group members.
  * 
  * @param name name of the destination tuplespace 
  * @param d operation descriptor
  * @throws LimeCommunicationException
  */
 public void sendReaction(String name, RemoteOp d) throws LimeCommunicationException
 {
  sendGroup(d); 
 }

 /**
  * Sends a reaction registration request to a specific host.
  * 
  * @param host destination host
  * @param d operation descriptor
  * @throws LimeCommunicationException
  */
 public void sendReaction(LimeServerID host, RemoteOp d) throws LimeCommunicationException
 {
  send(host, d); 	
 }
 
 /**
  * Allows to bind a message to a previous operation request (i.e, to gather results).
  * 
  * @param host destination host
  * @param d operation descriptor
  * @throws LimeCommunicationException
  */
 public void sendReply(LimeServerID host, RemoteOp d) throws LimeCommunicationException
 {
  send(host, d); 
 }
 
 /**
  * Returns a received RemoteOp (FIFO order, blocking).
  * 
  * @return RemoteOp
  */
 public RemoteOp getNextOp()
 {
  return (RemoteOp) queue.remove();	
 }
 
 /**
  * Deactivates the communication layer.
  */
 public void shutdown()
 {
  pur.shutdown();
  tpmr.shutdown();
 }
 
 /**
  * Returns whether an host is engaged.
  * 
  * @param host
  * @return <code>true</code> or <code>false</code>
  */
 public boolean queryHost(LimeServerID host)
 {
	 return true;
 }
 
 /**
  * Adds a new (discovered) host.
  * 
  * @param host
  */
 public void addHost(LimeServerID host)
 {

 }
  
 /**
  * Removes a (probable) dead host.
  * 
  * @param host
  */
 public void removeHost(LimeServerID host)
 {

 }
 
public HashSet<LimeServerID> multicast(LimeServerID[] hosts, RemoteOp op) throws LimeCommunicationException {
	return null;
}

 
 // unicast send 
 private void send(LimeServerID target, RemoteOp d) throws LimeCommunicationException
 { 
  // initialization	
  Connection c = null;	
  
  // total failure ?
  try { c = Connection.get(target); } 
  catch (IOException ioe) { throw new LimeCommunicationException(ioe, Failures.TOTAL); }

  // temporary variables
  boolean end = false;
  int retries = 0;
  
  // partial failure ?
  while (!end)
  {	
   try 
   { 
   	c.send(d); 
   	end = true;
   } 
   catch (IOException ioe) 
   { 
   	if (++retries >= LimeConstants.RETRIES) throw new LimeCommunicationException(ioe, Failures.PARTIAL); 
   }
  }
   
  // irrelevant failure ?
  try { c.finished(); } 
  catch (IOException ioe) { throw new LimeCommunicationException(ioe, Failures.IRRELEVANT); }
 }
  
 // multicast send; we rely on internal synchronization of datatagram sockets  
 private void sendGroup(RemoteOp d) throws LimeCommunicationException
 {
  // total failure ?	
  if (ssock == null) throw new LimeCommunicationException(Failures.TOTAL);	
 	
  // temporary variables
  byte[] data = Serializer.serialize(d);
  boolean end = false;
  int retries = 0;
 	 	 	  
  // partial failure ? either the message can be delivered to all the remote hosts, or it is delivered to none
  while (!end)
  {	
   try 
   { 
   	ssock.send(new DatagramPacket(data, data.length, maddr, mport)); 
    end = true;
   }
   catch (NullPointerException npe) { throw new LimeCommunicationException(npe, Failures.TOTAL); } // socket closed !
   catch (IOException ioe) 
   { 
    if (++retries >= LimeConstants.RETRIES) throw new LimeCommunicationException(ioe, Failures.PARTIAL); 
   }
  }
 }

@Override
public HashSet<LimeServerID> multicast(RemoteOp d) throws LimeCommunicationException {
	return null;
}

} 















